# coding=utf-8

import json
import time

try:
    from urllib import quote
    # import urllib2 as import_urllib
except ImportError:
    from urllib.parse import quote
    import urllib.request as import_urllib


class BmobObject:
    def __init__(self, type):
        self.__dict__["__type"] = type


class BmobPointer(BmobObject):
    def __init__(self, className, objectId):
        BmobObject.__init__(self, "Pointer")
        self.__dict__["className"] = className
        self.__dict__["objectId"] = objectId


class BmobFile(BmobObject):
    def __init__(self, url, filename=""):
        BmobObject.__init__(self, "File")
        self.__dict__["url"] = url
        self.__dict__["filename"] = filename


class BmobDate(BmobObject):
    def __init__(self, timestamp):
        BmobObject.__init__(self, "Date")
        if type(timestamp) == float or type(timestamp) == int:
            timestamp = time.strftime(
                '%Y-%m-%d %H:%M:%S', time.localtime(timestamp / 1000))
        self.__dict__["iso"] = timestamp


class BmobGeoPoint(BmobObject):
    def __init__(self, latitude, longitude):
        BmobObject.__init__(self, "GeoPoint")
        self.__dict__["latitude"] = latitude
        self.__dict__["longitude"] = longitude


def def_marshal(obj):
    return obj.__dict__


class BmobUpdater:
    @staticmethod
    def add(key, value, data=None):
        if data == None:
            data = {}
        data[key] = value
        return data

    @staticmethod
    def ensuerArray(self, value):
        if isinstance(value, BmobObject):
            value = [value.__dict__]
        elif isinstance(value, dict):
            value = [value]
        elif isinstance(value, list) or isinstance(value, tuple):
            objs = []
            for i in range(0, len(value)):
                obj = value[i]
                if isinstance(obj, BmobObject):
                    obj = obj.__dict__
                objs.append(obj)
            value = objs
        else:
            value = [value]
        return value

    @staticmethod
    def increment(key, number, data=None):
        return BmobUpdater.add(key, {"__op": "Increment", "amount": number}, data)

    @staticmethod
    def arrayAdd(key, value, data=None):
        return BmobUpdater.add(key, {"__op": "Add", "objects": BmobUpdater.ensuerArray(value)}, data)

    @staticmethod
    def arrayAddUnique(key, value, data=None):
        return BmobUpdater.add(key, {"__op": "AddUnique", "objects": BmobUpdater.ensuerArray(value)}, data)

    @staticmethod
    def arrayRemove(key, value, data=None):
        return BmobUpdater.add(key, {"__op": "Remove", "objects": BmobUpdater.ensuerArray(value)}, data)

    @staticmethod
    def addRelations(key, value, data=None):
        return BmobUpdater.add(key, {"__op": "AddRelation", "objects": BmobUpdater.ensuerArray(value)}, data)

    @staticmethod
    def removeRelations(key, value, data=None):
        return BmobUpdater.add(key, {"__op": "RemoveRelation", "objects": BmobUpdater.ensuerArray(value)}, data)


class BmobQuerier:
    def __init__(self):
        self.filter = {}
    # 基础

    def putWhereFilter(self, key, value=None, oper=None):
        if key == None or len(key) == 0 or value == None:
            return self
        if isinstance(value, BmobObject):
            value = value.__dict__
        if oper == None:
            self.filter[key] = value
        else:
            self.filter[key] = {oper: value}
        return self

    def addWhereEqualTo(self, key, value=None):
        if value == None:
            return self.addWhereNotExists(key)
        else:
            return self.putWhereFilter(key, value)

    def addWhereNotEqualTo(self, key, value=None):
        if value == None:
            return self.addWhereExists(key)
        else:
            return self.putWhereFilter(key, value, "$ne")
    # 比较

    def addWhereGreaterThan(self, key, value):
        return self.putWhereFilter(key, value, "$gt")

    def addWhereGreaterThanOrEqualTo(self, key, value):
        return self.putWhereFilter(key, value, "$gte")

    def addWhereLessThan(self, key, value):
        return self.putWhereFilter(key, value, "$lt")

    def addWhereLessThanOrEqualTo(self, key, value):
        return self.putWhereFilter(key, value, "$lte")
    # 关联

    def addWhereRelatedTo(self, table, objectId, key):
        return self.putWhereFilter(key, {"key": key, "object": {"__type": "Pointer", "className": table, "objectId": objectId}}, "$relatedTo")
    # 存在

    def addWhereExists(self, key, exists=True):
        return self.putWhereFilter(key, exists, "$exists")

    def addWhereNotExists(self, key):
        return self.addWhereExists(key, False)
    # 地理位置

    def addWhereNear(self, key, bmobGeoPointer, maxMiles=None, maxKM=None, maxRadians=None):
        near = {"$nearSphere": bmobGeoPointer.__dict__}
        if maxMiles != None:
            near["$maxDistanceInMiles"] = maxMiles
        if maxKM != None:
            near["$maxDistanceInKilometers"] = maxKM
        if maxRadians != None:
            near["$maxDistanceInRadians"] = maxRadians
        return self.putWhereFilter(key, near)

    def addWhereWithinGeoBox(self, southwest, northeast):
        return self.putWhereFilter(key, {"$box": [southwest.__dict__, northeast.__dict__]}, "$within")
    # 列表

    def addWhereContainedIn(self, key, value, isIn=True):
        if isIn:
            isIn = "$in"
        else:
            isIn = "$nin"
        return self.putWhereFilter(key, value, isIn)

    def addWhereNotContainedIn(self, key, value):
        return self.addWhereContainedIn(key, value, False)

    def addWhereContainsAll(self, key, value):
        return self.putWhereFilter(key, value, "$all")
    # 模糊查询

    def addWhereStrContains(self, key, value):
        return self.putWhereFilter(key, value, "$regex")
    # 子查询

    def addWhereMatchesSelect(self, key, innerQuery, innerKey, innerTable=None, isMatch=True):
        if isMatch:
            isMatch = "$select"
        else:
            isMatch = "$dontSelect"
        if isinstance(innerQuery, BmobQuerier):
            innerQuery = {"className": innerTable, "where": innerQuery.filter}
        return self.putWhereFilter(key, {"key": innerKey, "query": innerQuery}, isMatch)

    def addWhereInQuery(self, key, value, className=None, isIn=True):
        if isIn:
            isIn = "$inQuery"
        else:
            isIn = "$notInQuery"
        if isinstance(value, BmobQuerier):
            innerQuery = {"className": className, "where": value.filter}
        return self.putWhereFilter(key, value, isIn)


class HttpResponse:
    def __init__(self, code, status, headers, data, error=None):
        if code == None:
            code = -100
        if status == None:
            status = 'Unknown Error'
        if headers == None:
            headers = {}
        if data == None:
            data = ''
        # 错误代码
        self.code = code
        # 请求状态
        self.status = status
        # 请求头信息HttpMessage
        self.headers = headers
        # 字符串数据str
        self.stringData = data
		# 错误str
        self.err = error
        try:
            self.jsonData = json.loads(data)
            if 'results' in self.jsonData:
                self.queryResults = self.jsonData["results"]
            else:
                self.queryResults = None
            if 'count' in self.jsonData:
                self.statCount = self.jsonData["count"]
            else:
                self.statCount = 0
        except:
            self.jsonData = {}
            self.queryResults = None
            self.statCount = 0

    def updatedAt(self):
        if "updatedAt" in self.jsonData:
            return self.jsonData["updatedAt"]
        else:
            return None

    def createdAt(self):
        if "createdAt" in self.jsonData:
            return self.jsonData["createdAt"]
        else:
            return None

    def objectId(self):
        if "objectId" in self.jsonData:
            return self.jsonData["objectId"]
        else:
            return None

    def msg(self):
        if "msg" in self.jsonData:
            return self.jsonData["msg"]
        else:
            return None


def httpRequest(url, method='GET', headers=None, body=None, timeout=10):
    if headers == None:
        headers = {}
    if body != None:
        body = body.encode("utf-8")
    req = import_urllib.Request(url=url, data=body, headers=headers)
    if method != None:
        req.get_method = lambda: method
    try:
        res = import_urllib.urlopen(req, timeout=timeout)
        return HttpResponse(res.code, res.msg, res.headers, res.read())
    except import_urllib.URLError as e:
        try:
            if hasattr(e, "reason"):
                reason = e.reason
            else:
                reason = None
            return HttpResponse(e.code, e.msg, e.headers, e.read(), reason)
        except Exception as e:
            print("Req failed wih response.init:", e)
            errMsg = "Unknown Error"
            return HttpResponse(-3, errMsg, {}, errMsg, repr(e))
    else:
        errMsg = "Unknown Error"
        return HttpResponse(-4, errMsg, {}, errMsg, errMsg)

class SingletonBmob(object):
    _instance = None
    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(SingletonBmob,cls).__new__(cls, *args, **kwargs)
        return cls._instance

class Bmob:
    def __init__(self, appid, restkey):
        self.domain = 'https://api2.bmob.cn'
        self.headers = {"X-Bmob-Application-Id": appid,
                        "X-Bmob-REST-API-Key": restkey, "Content-Type": "application/json"}
        self.appid = appid
        self.restkey = restkey

    def setUserSession(self, sessionToken):
        self.headers["X-Bmob-Session-Token"] = sessionToken
        return self

    def setMasterKey(self, masterKey):
        self.headers["X-Bmob-Master-Key"] = masterKey
        return self

    # About user start
    def userSignUp(self, userInfo):
        return httpRequest(url=self.domain + '/1/users', method='POST', headers=self.headers, body=json.dumps(userInfo, default=def_marshal))

    def userLogin(self, username, password):
        return httpRequest(url=self.domain + '/1/login?username=' + quote(username) + '&password=' + quote(password), method='GET', headers=self.headers)

    def userLoginBySMS(self, mobile, smsCode, userInfo):
        userInfo["mobilePhoneNumber"] = mobile
        userInfo["smsCode"] = smsCode
        return self.userSignUp(userInfo)

    def userResetPasswordByEmail(self, email):
        return httpRequest(url=self.domain + '/1/requestPasswordReset', method='POST', headers=self.headers, body=json.dumps({"email": email}))

    def userResetPasswordBySMS(self, smsCode, password):
        return httpRequest(url=self.domain + '/1/resetPasswordBySmsCode/' + smsCode, method='PUT', headers=self.headers, body=json.dumps({"password": password}))

    def userResetPasswordByPWD(self, userId, session, oldPassword, newPassword):
        return httpRequest(url=self.domain + '/1/updateUserPassword/' + userId, method='PUT', headers=self.headers, body=json.dumps({"oldPassword": oldPassword, "newPassword": newPassword}))
    # About user over

    def sendCustomSMS(self, mobile, content):
        return httpRequest(url=self.domain + '/1/requestSms', method='POST', headers=self.headers, body=json.dumps({'mobilePhoneNumber': mobile, 'content': content}))

    def sendSMSCode(self, mobile, template):
        return httpRequest(url=self.domain + '/1/requestSmsCode', method='POST', headers=self.headers, body=json.dumps({'mobilePhoneNumber': mobile, "template": template}))

    def verifySMSCode(self, mobile, smsCode):
        return httpRequest(url=self.domain + '/1/verifySmsCode/' + smsCode, method='POST', headers=self.headers, body=json.dumps({'mobilePhoneNumber': mobile}))

    def payQuery(self, orderId):
        return httpRequest(url=self.domain + '/1/pay/' + orderId, method='GET', headers=self.headers)

    def cloudCode(self, funcName, body=None):
        if body == None:
            body = {}
        return httpRequest(url=self.domain + '/1/functions/' + funcName, method='POST', headers=self.headers, body=json.dumps(body, default=def_marshal))

    def getDBTime(self):
        return httpRequest(url=self.domain + '/1/timestamp/', method='GET', headers=self.headers)

    def batch(self, requests, isTransaction=None):
        if isTransaction == None or isTransaction == False or isTransaction == 0:
            isTransaction = ''
        else:
            isTransaction = '?isTransaction=1'
        return httpRequest(url=self.domain + '/1/batch' + isTransaction, method='POST', headers=self.headers, body=json.dumps(requests, default=def_marshal))

    def insert(self, className, data):
        if isinstance(data, dict):
            for k, v in data.items():
                if(isinstance(v, BmobObject)):
                    data[k] = v.__dict__
        return httpRequest(url=self.domain + '/1/classes/' + className, method='POST', headers=self.headers, body=json.dumps(data, default=def_marshal))

    def update(self, className, objectId, data):
        if isinstance(data, dict):
            for k, v in data.items():
                if(isinstance(v, BmobObject)):
                    data[k] = v.__dict__
        return httpRequest(url=self.domain + '/1/classes/' + className + '/' + objectId, method='PUT', headers=self.headers, body=json.dumps(data, default=def_marshal))

    def remove(self, className, objectId):
        return httpRequest(url=self.domain + '/1/classes/' + className + '/' + objectId, method='DELETE', headers=self.headers)

    def find(self, table, where=None, limit=None, skip=None, order=None, include=None, keys=None, count=None, groupby=None, groupcount=None, min=None, max=None, sum=None, average=None, having=None, objectId=None):
        try:
            url = self.domain + '/1/classes/' + table
            if objectId != None:
                url += '/' + objectId
            else:
                params = ''
                if limit != None:
                    params += '&limit=' + str(limit)
                if skip != None:
                    params += '&skip=' + str(skip)
                if count != None:
                    params += '&count=' + str(count)
                if groupby != None:
                    params += '&groupby=' + quote(groupby)
                if groupcount != None and (groupcount == True or groupcount == 1):
                    params += '&groupcount=true'
                if sum != None:
                    params += '&sum=' + quote(sum)
                if average != None:
                    params += '&average=' + str(average)
                if max != None:
                    params += '&max=' + str(max)
                if min != None:
                    params += '&min=' + str(min)
                if having != None:
                    params += '&having=' + str(having)
                if order != None:
                    params += '&order=' + str(order)
                if keys != None:
                    params += '&keys=' + str(keys)
                if include != None:
                    params += '&include=' + str(include)
                if where != None:
                    if isinstance(where, BmobQuerier):
                        where = where.filter
                    params += '&where=' + \
                        quote(json.dumps(where, default=def_marshal))
                if len(params) != 0:
                    url += '?' + params[1:]
            return httpRequest(url=url, method='GET', headers=self.headers)
        except Exception as e:
            print(repr(e))
            msg = 'Bad Query'
            return HttpResponse(-1, msg, None, msg, msg)

    def findOne(self, className, objectId):
        return httpRequest(url=self.domain + '/1/classes/' + className + '/' + objectId, method='GET', headers=self.headers)
